package com.houhanzhi.service.impl;

import com.houhanzhi.config.RabbitMQConfig;
import com.houhanzhi.constant.TimeConstant;
import com.houhanzhi.controller.request.ConfirmOrderRequest;
import com.houhanzhi.controller.request.ProductOrderPageRequest;
import com.houhanzhi.enums.*;
import com.houhanzhi.exception.BizException;
import com.houhanzhi.interceptor.LoginInterceptor;
import com.houhanzhi.manager.ProductManager;
import com.houhanzhi.manager.ProductOrderManager;
import com.houhanzhi.model.EventMessage;
import com.houhanzhi.model.LoginUser;
import com.houhanzhi.model.ProductDO;
import com.houhanzhi.model.ProductOrderDO;
import com.houhanzhi.response.JsonData;
import com.houhanzhi.service.ProductOrderService;
import com.houhanzhi.strategy.PayFactory;
import com.houhanzhi.util.CommonUtil;
import com.houhanzhi.util.IDUtil;
import com.houhanzhi.util.JsonUtil;
import com.houhanzhi.vo.PayInfoVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class ProductOrderServiceImpl implements ProductOrderService {

    @Autowired
    private ProductOrderManager productOrderManager;

    @Autowired
    private ProductManager productManager;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitMQConfig rabbitMQConfig;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private PayFactory payFactory;

    /**
     * 分页
     *
     * @param request
     * @return
     */
    @Override
    public Map<String, Object> page(ProductOrderPageRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        return productOrderManager.page(request.getPage(), request.getSize(), accountNo, request.getState());
    }

    /**
     * 查询订单状态
     *
     * @param outTradeNo
     * @return
     */
    @Override
    public String queryProductOrderState(String outTradeNo) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        ProductOrderDO productOrderDO = productOrderManager.findByOutTradeNoAndAccountNo(outTradeNo, accountNo);
        if (Objects.isNull(productOrderDO)) {
            return null;
        }
        return productOrderDO.getState();
    }

    /**
     * 下单
     * 防重提交（重点）TODO
     * 获取最新的流量包价格
     * 订单验价
     * 如果有优惠券或者其他抵扣
     * 验证前端显示和后台计算价格
     * 创建订单对象保存数据库
     * 发送延迟消息-用于自动关单（重点）TODO
     * 创建支付信息-对接三方支付（重点）TODO
     * 回调更新订单状态（重点）TODO
     * 支付成功创建流量包（重点）TODO
     *
     * @param request
     * @return
     */
    @Override
    @Transactional
    public JsonData confirmOrder(ConfirmOrderRequest request) {

        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        // 生成订单号
        String outTradeNo = CommonUtil.getStringNumRandom(32);
        // 获取流量包信息
        ProductDO productDO = productManager.findDetailById(request.getProductId());
        // 验价
        this.checkPrice(request, productDO);

        // 创建订单
        ProductOrderDO productOrderDO = this.saveProductOrder(request, loginUser, outTradeNo, productDO);

        // 创建支付对象 TODO
        final PayInfoVO payInfoVO = PayInfoVO.builder()
                .accountNo(loginUser.getAccountNo())
                .outTradeNo(outTradeNo)
                .payFee(request.getPayAmount())
                .payType(request.getPayType())
                .clientType(request.getClientType())
                .title(productDO.getTitle())
                .description("")
                .orderPayTimeOutMills(TimeConstant.ORDER_PAY_TIMEOUNT_MILLS)
                .build();

        // 发送延迟消息-用于自动关单
        EventMessage eventMessage = EventMessage.builder()
                .eventMessageType(EventMessageTypeEnum.PRODUCT_ORDER_NEW.name())
                .accountNo(loginUser.getAccountNo())
                .bizId(outTradeNo)
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .build();
        rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(), rabbitMQConfig.getOrderCloseDelayRoutingKey(), eventMessage);

        return JsonData.buildSuccess();
    }

    /**
     * 处理队列里面的订单相关消息
     *
     * @param eventMessage
     */
    @Override
    public void handleProductOrderMessage(EventMessage eventMessage) {
        String eventMessageType = eventMessage.getEventMessageType();
        if (eventMessageType.equalsIgnoreCase(EventMessageTypeEnum.PRODUCT_ORDER_NEW.name())) {
            // 关闭订单
            this.closeProductOrder(eventMessage);
        } else if (eventMessageType.equalsIgnoreCase(EventMessageTypeEnum.PRODUCT_ORDER_PAY.name())) {
            //订单已经支付，更新订单状态
            String outTradeNo = eventMessage.getBizId();
            Long accountNo = eventMessage.getAccountNo();
            int rows = productOrderManager.updateOrderPayState(outTradeNo, accountNo,
                    ProductOrderStateEnum.PAY.name(), ProductOrderStateEnum.NEW.name());
            log.info("订单更新成功:rows={},eventMessage={}", rows, eventMessage);
        }
    }

    /**
     * //延迟消息的时间 需要比订单过期时间长一点，否则会造成，在订单的有效支付期内，订单被关闭
     * <p>
     * //查询订单是否存在，如果已经支付则正常结束
     * //如果订单未支付，主动调用第三方支付平台查询订单状态，确认未支付，本地取消订单
     * //如果第三方平台已经支付，主动的把订单状态改成已支付，造成该原因的情况可能是支付通道回调有问题，然后触发支付后的动作，如何触发？RPC还是？
     *
     * @param eventMessage
     */
    private boolean closeProductOrder(EventMessage eventMessage) {
        // 订单号
        String outTradeNo = eventMessage.getBizId();
        Long accountNo = eventMessage.getAccountNo();
        // 查询订单
        ProductOrderDO productOrderDO = productOrderManager.findByOutTradeNoAndAccountNo(outTradeNo, accountNo);
        if (Objects.isNull(productOrderDO)) {
            log.warn("订单不存在");
            return true;
        }
        if (productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.PAY.name())) {
            log.info("直接确认消息，订单已经支付:{}", eventMessage);
            return true;
        }

        //未支付，需要向第三方支付平台查询状态 TODO
        if (productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.NEW.name())) {
            handleNoPayDelayCloseOrder(eventMessage, productOrderDO, outTradeNo, accountNo);
        }
        return true;
    }

    private boolean handleNoPayDelayCloseOrder(EventMessage eventMessage, ProductOrderDO productOrderDO, String outTradeNo, Long accountNo) {
        //向第三方查询状态
        PayInfoVO payInfoVO = new PayInfoVO();
        payInfoVO.setPayType(productOrderDO.getPayType());
        payInfoVO.setOutTradeNo(outTradeNo);
        payInfoVO.setAccountNo(accountNo);
        // 需要向第三方支付平台查询状态
        String payResult = payFactory.queryPayState(payInfoVO);
        if (StringUtils.isBlank(payResult)) {
            //如果为空，则未支付成功，本地取消订单
            productOrderManager.updateOrderPayState(outTradeNo, accountNo, ProductOrderStateEnum.CANCEL.name(), ProductOrderStateEnum.NEW.name());
            log.info("未支付成功，本地取消订单:{}", eventMessage);
        } else {
            //支付成功，主动把订单状态更新成支付
            log.warn("支付成功，但是微信回调通知失败，需要排查问题:{}", eventMessage);
            productOrderManager.updateOrderPayState(outTradeNo, accountNo, ProductOrderStateEnum.PAY.name(), ProductOrderStateEnum.NEW.name());
            //触发支付成功后的逻辑
            Map<String, Object> content = new HashMap<>(4);

            content.put("outTradeNo", outTradeNo);
            content.put("buyNum", productOrderDO.getBuyNum());
            content.put("accountNo", productOrderDO.getAccountNo());
            content.put("product", productOrderDO.getProductSnapshot());

            // 构建消息：更新订单状态
            EventMessage payEventMessage = EventMessage.builder()
                    .bizId(outTradeNo)
                    .accountNo(accountNo)
                    .messageId(outTradeNo)
                    .content(JsonUtil.obj2Json(content))
                    .eventMessageType(EventMessageTypeEnum.PRODUCT_ORDER_PAY.name())
                    .build();
            // 通过 redis，如果key 不存在，则设置成功，返回 true
            boolean flag = redisTemplate.opsForValue()
                    .setIfAbsent(outTradeNo,
                            "OK",
                            3,
                            TimeUnit.DAYS);
            if (flag) {
                // 生成流量包权益
                rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(),
                        rabbitMQConfig.getOrderUpdateTrafficRoutingKey(),
                        payEventMessage);
                return false;
            }
        }
        return true;
    }

    /**
     * 保存订单
     *
     * @param request
     * @param loginUser
     * @param outTradeNo
     * @param productDO
     * @return
     */
    private ProductOrderDO saveProductOrder(ConfirmOrderRequest request, LoginUser loginUser, String outTradeNo, ProductDO productDO) {

        ProductOrderDO productOrderDO = new ProductOrderDO();
        // 设置用户信息
        productOrderDO.setAccountNo(loginUser.getAccountNo());
        productOrderDO.setNickname(loginUser.getUsername());
        // 设置商品信息
        productOrderDO.setProductId(productDO.getId());
        productOrderDO.setProductTitle(productDO.getTitle());
        productOrderDO.setProductAmount(productDO.getAmount());
        // 商品快照
        productOrderDO.setProductSnapshot(JsonUtil.obj2Json(productDO));
        // 设置订单信息
        productOrderDO.setBuyNum(request.getBuyNum());
        productOrderDO.setOutTradeNo(outTradeNo);
        productOrderDO.setDel(0);
        productOrderDO.setCreateTime(new Date());
        // 实际支付总价
        productOrderDO.setPayAmount(request.getPayAmount());
        // 总价，没有使用优惠券
        productOrderDO.setTotalAmount(request.getTotalAmount());
        // 订单状态，未支付
        productOrderDO.setState(ProductOrderStateEnum.NEW.name());
        // 支付类型
        productOrderDO.setPayType(ProductOrderPayTypeEnum.valueOf(request.getPayType()).name());
        // 发票信息
        productOrderDO.setBillType(BillTypeEnum.valueOf(request.getBillType()).name());
        productOrderDO.setBillContent(request.getBillContent());
        productOrderDO.setBillHeader(request.getBillHeader());
        productOrderDO.setBillReceiverEmail(request.getBillReceiverEmail());
        productOrderDO.setBillReceiverPhone(request.getBillReceiverPhone());

        // 插入数据库
        final int rows = productOrderManager.add(productOrderDO);
        return productOrderDO;
    }

    /**
     * 验价
     *
     * @param request
     * @param productDO
     */
    private void checkPrice(ConfirmOrderRequest request, ProductDO productDO) {
        // 商品价格：购买数量*商品当前的实际价格
        BigDecimal productPrice = BigDecimal.valueOf(request.getBuyNum()).multiply(productDO.getAmount());
        // 对比前端传递的总价和后端计算的总价是否一致，如果有优惠券，也在这边计算
        if (productPrice.compareTo(request.getPayAmount()) != 0) {
            log.error("验证价格失败:{}", request);
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_PRICE_FAIL);
        }
    }


    /**
     * 微信回调通知
     *
     * @param payType
     * @param paramsMap
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public JsonData processOrderCallbackMsg(ProductOrderPayTypeEnum payType, Map<String, String> paramsMap) {
        // 获取商户订单号
        String outTradeNo = paramsMap.get("out_trade_no");
        // 交易的状态
        String tradeState = paramsMap.get("trade_state");
        Long accountNo = Long.valueOf(paramsMap.get("account_no"));

        /**
         * 由业务性能决定哪种方式,空间换时间，时间换空间
         *  方式一：商品信息/订单信息 可以由消费者那边 远程调用feign进行获取，多了一次开销
         *  方式三：订单生成，商品信息进行快照存储到订单（空间换时间，推荐）
         *
         */
        Map<String, Object> content = new HashMap<>(4);
        ProductOrderDO productOrderDO = productOrderManager.findByOutTradeNoAndAccountNo(outTradeNo, accountNo);

        content.put("outTradeNo", outTradeNo);
        content.put("buyNum", productOrderDO.getBuyNum());
        content.put("accountNo", productOrderDO.getAccountNo());
        content.put("product", productOrderDO.getProductSnapshot());

        // 构建消息
        EventMessage eventMessage = EventMessage.builder()
                .bizId(outTradeNo)
                .accountNo(accountNo)
                .messageId(outTradeNo)
                .content(JsonUtil.obj2Json(content))
                .eventMessageType(EventMessageTypeEnum.PRODUCT_ORDER_PAY.name())
                .build();

        if (payType.name().equalsIgnoreCase(ProductOrderPayTypeEnum.ALI_PAY.name())) {
            // 支付宝支付 TODO
        } else if (payType.name().equalsIgnoreCase(ProductOrderPayTypeEnum.WECHAT_PAY.name())) {
            // 微信支付
            /**
             * 交易状态，枚举值：
             * SUCCESS：支付成功
             * REFUND：转入退款
             * NOTPAY：未支付
             * CLOSED：已关闭
             * REVOKED：已撤销（付款码支付）
             * USERPAYING：用户支付中（付款码支付）
             * PAYERROR：支付失败(其他原因，如银行返回失败)
             */
            if ("SUCCESS".equalsIgnoreCase(tradeState)) {
                // 微信支付回调通知-MQ重复发送问题：保证 MQ 发送消息的幂等性
                // 通过 redis，如果key 不存在，则设置成功，返回 true
                boolean flag = redisTemplate.opsForValue()
                        .setIfAbsent(outTradeNo,
                                "OK",
                                3,
                                TimeUnit.DAYS);
                if (flag) {
                    // 推送订单更新以及流量包权益消息
                    rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(),
                            rabbitMQConfig.getOrderUpdateTrafficRoutingKey(),
                            eventMessage);
                    return JsonData.buildSuccess();
                }
            }
        }

        return JsonData.buildResult(BizCodeEnum.PAY_ORDER_CALLBACK_NOT_SUCCESS);
    }
}
